package org.hzz.locks;

import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @author <a href="mailto:1193094618@qq.com">pkmer</a>
 * <br>
 * <a href = "https://gitee.com/developeros/videos-online">Code Repository</a>
 * At 2024/9/7
 * 自旋锁实现
 */
@Slf4j
public class SpinLock implements Lock {

    /**
     * the number of times to spin in a busy-wait loop before yielding.
     */
    private final int SPIN_TIMES = 1000;

    private final AtomicBoolean locked = new AtomicBoolean(false);
    @Override
    public void lock() {
        while(true){
            // spin in a busy-wait loop for yielding
            for(int i = 0; i< SPIN_TIMES; i++){
                if(locked.compareAndSet(false,true)){
                    return;
                }
            }
            Thread.yield();
//            log.info("{}正在自旋获取锁",Thread.currentThread().getName());
        }
    }

    /**
     * 支持中断的锁
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        while(true){
            // 在每一轮自旋之前判断是否被中断
            if(Thread.interrupted()){
                throw new InterruptedException();
            }
            // spin in a busy-wait loop for yielding
            for(int i = 0; i< SPIN_TIMES; i++){
                if(locked.compareAndSet(false,true)){
                    return;
                }
            }
            Thread.yield();
        }
    }

    @Override
    public boolean tryLock() {
        return locked.compareAndSet(false,true);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        long deadline = System.nanoTime() + unit.toNanos(time);

        while(true){
            if(Thread.interrupted()){
                throw new InterruptedException();
            }
            long nanoRemaing = deadline - System.nanoTime();
            if(nanoRemaing < 100){
                // 很少的时间，直接自选，直到时间耗尽
                do{
                    if(locked.compareAndSet(false,true)){
                        return true;
                    }
                    nanoRemaing = deadline - System.nanoTime();
                }while(nanoRemaing > 0);

                return false;
            }else{
                int amountTimes = (int)Math.min(nanoRemaing, SPIN_TIMES);
                for(int i = 0; i< amountTimes;i++){
                    if(locked.compareAndSet(false,true)){
                        return true;
                    }
                }
                Thread.yield();
            }
        }

    }

    @Override
    public void unlock() {
        locked.compareAndSet(true,false);
    }

    @Override
    public Condition newCondition() {
        return new ConditionObject();
    }


    private static class WaitThread{
        final Thread thread;
        volatile boolean signaled;

        WaitThread(Thread thread){
            this.thread = thread;
        }
    }

    private  class ConditionObject extends ConcurrentLinkedQueue<WaitThread> implements  Condition{

        private void checkLock(){
            // 确保当前线程拥有锁
            if(!locked.get()){
                throw new IllegalMonitorStateException();
            }
        }

        private void enqueueAndUnlock(WaitThread waitThread){
            add(waitThread);
            if(!locked.compareAndSet(true,false)){
                if(!remove(waitThread)){
                    signal();
                }
                throw new IllegalMonitorStateException();
            }
        }

        /**
         * await是必须释放锁
         * @throws InterruptedException
         */
        @Override
        public void await() throws InterruptedException {
            checkLock();
            if(Thread.interrupted()){
                throw new InterruptedException();
            }
            WaitThread waitThread = new WaitThread(Thread.currentThread());
            enqueueAndUnlock(waitThread);
            boolean failed = false;
            try{
                // 阻塞当前线程
                LockSupport.park(this);
                if(Thread.interrupted()){
                    failed = true;
                    throw new InterruptedException();
                }
            }catch (RuntimeException | Error e) {
                failed = true;
                throw e;
            } finally {
                lock();
                // 移除队列
                if(!waitThread.signaled){
                    // 进入到这段代码之后，说明线程不是被正常通知信号唤醒的
                    // 中断唤醒的是没有通过sinal方法poll
                    failed = !remove(waitThread);
                }

                if(failed){
                    signal();
                }
            }
        }

        @Override
        public void awaitUninterruptibly() {
            checkLock();
            WaitThread waitThread = new WaitThread(Thread.currentThread());
            boolean interrupted = false;
            boolean failed = false;
            enqueueAndUnlock(waitThread);

            try{
                // 一直循环直到被唤醒
                do{
                    LockSupport.park(this);
                    if(Thread.interrupted()){
                        interrupted = true;
                    }
                }while(!waitThread.signaled);
            }catch (RuntimeException | Error e){
                failed = true;
                throw e;
            }
            finally {
                lock();

                // 移除队列
                if(!waitThread.signaled){
                    // 进入到这段代码之后，说明线程不是被正常通知信号唤醒的
                    // 中断唤醒的是没有通过sinal方法poll
                    failed = !remove(waitThread);
                }

                if(failed){
                    signal();
                }

                if(interrupted){
                    waitThread.thread.interrupt();
                }
            }

        }


        @Override
        public long awaitNanos(long nanosTimeout) throws InterruptedException {
            checkLock();
            if(Thread.interrupted()){
                throw new InterruptedException();
            }
            WaitThread waitThread = new WaitThread(Thread.currentThread());
            enqueueAndUnlock(waitThread);
            long deadline = System.nanoTime() + nanosTimeout;
            boolean failed = false;
            try {
                LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted()) {
                    throw new InterruptedException();
                }
                return deadline - System.nanoTime();
            } catch (RuntimeException | Error e){
                failed = true;
                throw e;
             }finally {
                lock();
                if(!waitThread.signaled){
                    // 进入到这段代码之后，说明线程不是被正常通知信号唤醒的
                    // 中断唤醒的是没有通过sinal方法poll
                    failed = !remove(waitThread);
                }

                if(failed){
                    // 传递唤醒给下一个等待者
                    signal();
                }
            }
        }

        @Override
        public boolean await(long time, TimeUnit unit) throws InterruptedException {
            return awaitNanos(unit.toNanos(time)) > 0;
        }

        @Override
        public boolean awaitUntil(Date deadline) throws InterruptedException {
            long nanosTimeout = TimeUnit.MILLISECONDS.toNanos(deadline.getTime() - System.currentTimeMillis());
            return awaitNanos(nanosTimeout) > 0;
        }

        @Override
        public void signal() {
            checkLock();
            // 获取队列中头部等待的线程对象，并将其从队列中移除
            WaitThread waitThread = poll();
            if(waitThread != null){
                waitThread.signaled = true;
                LockSupport.unpark(waitThread.thread);
            }
        }

        @Override
        public void signalAll() {
            checkLock();
            while(true){
                // 获取队列中头部等待的线程对象，并将其从队列中移除
                WaitThread waitThread = poll();
                if(waitThread != null){
                    waitThread.signaled = true;
                    LockSupport.unpark(waitThread.thread);
                }
            }
        }
    }
}
